package com.gcit.seniorWish.web.config.web;

import com.gcit.seniorWish.web.config.properties.GunsProperties;
import com.gcit.seniorWish.web.core.intercept.PortalUserFilter;
import com.gcit.seniorWish.web.core.intercept.UserFilter;
import com.gcit.seniorWish.web.core.shiro.ShiroDbRealm;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro权限管理的配置
 *
 * @author fengshuonan
 * @date 2016年11月14日 下午3:03:44
 */
@Configuration
public class ShiroConfig {

    /**
     * https://www.cnblogs.com/expiator/p/8651798.html
     *  shiro是一个安全框架，可以进行角色，权限管理，shiro的主要功能为：
     *  Shiro的核心通过过滤器Filter实现。Shiro中的Filter是通过URL规则来进行过滤和权限校验
     *  1、Authentication（认证）：用户身份识别，通常被称为用户“登录”
     *  2、Authorization（授权）：访问控制。比如某个用户是否具有某个操作的使用权限。
     *  3、Session Management（会话管理）：特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
     *  4、Cryptography（加密）：在对数据源使用加密算法加密的同时，保证易于使用。
     *  Subject：当前用户，Subject可以是一个人，也可以是第三方服务
     *  SecurityManager：管理所有Subject，可以配合内部安全组件。
     *  principals：身份，即主体的标识属性，可以是任何东西，如用户名、邮箱等，唯一即可。
     *      一个主体可以有多个principals，但只有一个Primary principals，一般是用户名/密码/手机号
     *  4.credentials：证明/凭证，即只有主体知道的安全值，如密码/数字证书等。最常见的principals和credentials组合就是用户名/密码了。
        5.Realms：用于进行权限信息的验证，需要自己实现。
        6.Realm 本质上是一个特定的安全 DAO：它封装与数据源连接的细节，得到Shiro 所需的相关的数据。
        在配置 Shiro 的时候，你必须指定至少一个Realm 来实现认证（authentication）和/或授权（authorization）。
        我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份，Authorization 是授权访问控制，用于对用户进行的操作授权，证明该用户是否允许进行当前操作，如访问某个链接，某个资源文件等。
     7.SimpleHash，可以通过特定算法(比如md5)配合盐值salt，对密码进行多次加密。
     */


    /**
	 * 安全管理器 管理所有的subject()
	 */
	@Bean
	public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(this.shiroDbRealm());
		securityManager.setCacheManager(cacheShiroManager);
		securityManager.setRememberMeManager(rememberMeManager);
		securityManager.setSessionManager(sessionManager);
		return securityManager;
	}
	/**
	 * spring session管理器（多机环境）
	 */

	@Bean
	@ConditionalOnProperty(prefix = "guns", name = "spring-session-open", havingValue = "true")
	public ServletContainerSessionManager servletContainerSessionManager() {
		return new ServletContainerSessionManager();
	}

	/**
	 * session管理器(单机环境)
	 */
	@Bean
	@ConditionalOnProperty(prefix = "guns", name = "spring-session-open",havingValue = "false")
	public DefaultWebSessionManager defaultWebSessionManager(CacheManager cacheShiroManager, GunsProperties gunsProperties) {

		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		sessionManager.setCacheManager(cacheShiroManager);
		sessionManager.setSessionValidationInterval(gunsProperties.getSessionValidationInterval() * 1000);
		sessionManager.setGlobalSessionTimeout(gunsProperties.getSessionInvalidateTime() * 1000);
		sessionManager.setDeleteInvalidSessions(true);
		sessionManager.setSessionValidationSchedulerEnabled(true);
		Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
		cookie.setName("shiroCookie");
		cookie.setHttpOnly(true);
		sessionManager.setSessionIdCookie(cookie);
		return sessionManager;
	}


	/**
	 * 缓存管理器 使用Ehcache实现
	 */
	@Bean
	public CacheManager getCacheShiroManager(EhCacheManagerFactoryBean ehcache) {
		EhCacheManager ehCacheManager = new EhCacheManager();
		ehCacheManager.setCacheManager(ehcache.getObject());
		return ehCacheManager;
	}

	/**
	 * 项目自定义的Realm
	 */
	@Bean
	public ShiroDbRealm shiroDbRealm() {
		return new ShiroDbRealm();
	}

	/**
	 * rememberMe管理器, cipherKey生成见{@code Base64Test.java}
	 */
	@Bean
	public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
		CookieRememberMeManager manager = new CookieRememberMeManager();
		manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
		manager.setCookie(rememberMeCookie);
		return manager;
	}

	/**
	 * 记住密码Cookie
	 */
	@Bean
	public SimpleCookie rememberMeCookie() {
		SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
		simpleCookie.setHttpOnly(true);
		simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
		return simpleCookie;
	}

	/**
	 * Shiro的过滤器链
	 */
	@Bean
	public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
		shiroFilter.setSecurityManager(securityManager);

		/**
		 * 默认的登陆访问url
		 */
		shiroFilter.setLoginUrl("/login");
		/**
		 * 登陆成功后跳转的url
		 */
		shiroFilter.setSuccessUrl("/");
		/**
		 * 没有权限跳转的url
		 */
		shiroFilter.setUnauthorizedUrl("/global/error");

		/**
		 * 覆盖默认的user拦截器(默认拦截器解决不了ajax请求 session超时的问题,若有更好的办法请及时反馈作者)
		 */
		FormAuthenticationFilter portalAuth = new FormAuthenticationFilter();
		portalAuth.setLoginUrl("/portal/login");
		portalAuth.setSuccessUrl("/portal");
		HashMap<String, Filter> myFilters = new HashMap<>();
		myFilters.put("user", new UserFilter());
		myFilters.put("portalUser", new PortalUserFilter());
		shiroFilter.setFilters(myFilters);
		/**
		 * 配置shiro拦截器链
		 *
		 * anon  不需要认证
		 * authc 需要认证
		 * user  验证通过或RememberMe登录的都可以
		 *
		 * 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
		 *
		 * 顺序从上到下,优先级依次降低
		 *
		 */
		Map<String, String> hashMap = new LinkedHashMap<>();
		hashMap.put("/static/**", "anon");
		hashMap.put("/api/**", "anon");
		hashMap.put("swagger-ui.html", "anon");
		hashMap.put("/global/sessionError", "anon");
		hashMap.put("/kaptcha", "anon");
		hashMap.put("/file/**", "anon");
		hashMap.put("/login", "anon");
		hashMap.put("/portal/login", "anon");
		hashMap.put("/api/*", "anon");
		//查分页面不需要登陆验证
		hashMap.put("/portal/scoreInquiry", "anon");
		//考生头像不需要登陆
		hashMap.put("/seniorExaminee/examineeHeadPic/*", "anon");
		hashMap.put("/portal/**", "portalUser");
		hashMap.put("/**", "user");
		shiroFilter.setFilterChainDefinitionMap(hashMap);
		return shiroFilter;
	}
	
	
	/**
	 * 在方法中 注入 securityManager,进行代理控制
	 */
	@Bean
	public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
		MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
		bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
		bean.setArguments(new Object[]{securityManager});
		return bean;
	}

	/**
	 * Shiro生命周期处理器:
	 * 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
	 * 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
	 */
	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * 启用shrio授权注解拦截方式，AOP式方法级权限检查
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
				new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

}
